<script lang="ts" setup>
import { file2dataurl, triggerFile } from '@/utils/file'
import { Button, Modal, Space } from 'ant-design-vue'

import { computed, ref } from 'vue'

import { VueCropper } from 'vue-cropper'

import 'vue-cropper/dist/index.css'

interface Handlers {
  beforeApply?: (file: File) => boolean | Promise<boolean>
  success?: (file: File) => any
}
export interface Options extends Handlers {
  img?: File | string
  fileName?: string
  /**
   * 裁剪生成图片的格式(jpg 需要传入jpeg)
   * @default png
   */
  outputType?: 'jpeg' | 'png' | 'webp'
  width?: number
  height?: number
  /** @default true */
  autoCrop?: boolean
  /** @default true */
  centerBox?: boolean
  /** @default true */
  fixed?: boolean
  autoCropWidth?: number
  autoCropHeight?: number
  /** @default false */
  destroyOnClose?: boolean
}
export interface Exposed {
  openModal: (userOptions: Options) => void
}
interface Priview {
  div?: {
    height: string
    width: string
  }
  h?: number
  html?: string
  img?: {
    height: string
    transform: string
    width: string
  }
  url?: string
  w?: number
}
defineOptions({
  name: 'ImgCropModal',
})
const emit = defineEmits<{
  success: [file: File]
}>()
const fileName = ref<string>()
const option = ref(getInitialOption())
const previews = ref<Priview>({})
const changedImg = ref<string>()
const applyOptions = computed(() => {
  const { autoCropHeight, autoCropWidth, height, width, ...passThrough }
    = option.value
  passThrough.img = changedImg.value || passThrough.img
  return {
    ...passThrough,
    autoCropHeight: autoCropHeight || height,
    autoCropWidth: autoCropWidth || width,
    fixedNumber: width && height ? [width, height] : [1, 1],
  }
})
const cropper = ref<any>()
const showCrop = ref<boolean>(true)
const open = ref<boolean>(false)
defineExpose({
  async openModal(userOptions: Options) {
    if (userOptions.img) {
      if (userOptions.img instanceof File) {
        getFileName(userOptions.img.name)
        userOptions.img = await file2dataurl(userOptions.img)
      }
      else {
        const slashIndex = userOptions.img.lastIndexOf('/')
        if (~slashIndex) {
          getFileName(userOptions.img.slice(slashIndex + 1))
        }
      }
    }
    showCrop.value = true
    open.value = true
    option.value = Object.assign({}, option.value, userOptions)
  },
})
function handleCancel() {
  open.value = false
  if (option.value.destroyOnClose) {
    showCrop.value = false
  }
  option.value = getInitialOption()
  changedImg.value = void 0
  fileName.value = void 0
  previews.value = {}
}
async function handleOk() {
  if (cropper.value) {
    cropper.value.getCropBlob((blob: Blob) => {
      if (blob) {
        const name = fileName.value || getFileName('cropped.png')
        const file = new File([blob], name, { type: blob.type })
        emit('success', file)
        if (option.value.success) {
          option.value.success(file)
        }
        handleCancel()
      }
    })
  }
}
function getFileName(name: string) {
  const [baseName] = name.split('.')
  const ext = option.value.outputType
  fileName.value = `${baseName}.${ext}`
  return fileName.value
}
function getInitialOption(): Options {
  return {
    autoCrop: true,
    centerBox: true,
    outputType: 'png',
    fixed: true,
    destroyOnClose: false,
  }
}
function realTime(data: Priview) {
  previews.value = data
}
async function handleChangeFile() {
  const files = await triggerFile('image', false)
  const [file] = files
  if (typeof option.value.beforeApply === 'function') {
    const valid = await option.value.beforeApply(file!)
    if (!valid) {
      return
    }
  }
  changedImg.value = await file2dataurl(file!)
  getFileName(file!.name)
}
</script>

<template>
  <Modal
    v-model:open="open"
    :mask-closable="false"
    :width="860"
    :z-index="9999999999"
    title="裁剪图片"
    @cancel="handleCancel"
    @ok="handleOk"
  >
    <div class="box-border img-crop-modal">
      <Space>
        <Button size="middle" type="primary" @click="handleChangeFile">
          选择/更换图片
        </Button>
        <div class="text-gray-500">
          滚动鼠标放大或缩小图片
        </div>
      </Space>
      <div v-if="showCrop" class="box-border my-10 flex flex-row items-start">
        <div class="flex-none box-border" style="width: 360px; height: 360px">
          <VueCropper
            ref="cropper"
            mode="cover"
            v-bind="applyOptions"
            @real-time="realTime"
          />
        </div>
        <div
          :style="{
            width: `${previews.w}px`,
            height: `${previews.h}px`,
            overflow: 'hidden',
            marginLeft: '80px',
          }"
          class="show-priview bg-gray-300"
        >
          <div :style="previews.div">
            <img :src="previews.url" :style="previews.img">
          </div>
        </div>
      </div>
    </div>
  </Modal>
</template>

<style lang="css">
.img-crop-modal img {
  max-width: unset;
}
</style>
